#include <kutils.h>
#include "memory.h"
#include "irq.h"
#include "display.h"
#include "rtc.h"
#include "paging.h"
#include "task.h"

#define BCD2BIN(val) (((val) & 0x0F) + ((val) >> 4) * 10)

// There are twelve months but the first month is 01 not 00.
// Since month '00' doesn't exist, it is used here for a little easter-egg.
s8int* month_names[13] = 
{
	"4 8 15 16 23 42","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};
u32int month_days[13] =
{
	108, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
volatile u32int years, months, days, hours, minutes, seconds = 0;
timed_callback_t null_callback;
volatile u32int rtc_ticks = 0;
volatile u8int rtc_initalized = 0;

void rtc_write_time(void);
void rtc_read_time(void);
void rtc_handler(struct regs *r);
void rtc_program_timer(u8int rate);

// should be entered once every half second
void rtc_handler(struct regs *r)
{
	if(!r){return;}
	rtc_ticks++;

	if (rtc_ticks % 2 == 0)
   {
		seconds++;
		if(seconds > 59)
		{
			minutes++;
			seconds = 0;
		}
		if(minutes > 59)
		{
			hours++;
			minutes = 0;
		}
		if(hours > 23)
		{
			days++;
			hours = 0;
		}
		if(days > month_days[months])
		{
			months++;
			days = 1;
		}
		if(months > 12)
		{
			years++;
			months = 1;
		}
	}

	// callbacks checked once every second
	if(rtc_ticks % 2 == 0)
	{
		timed_callback_t* tmp = &null_callback;
		timed_callback_t* prev = 0;
		while(tmp->next != &null_callback)
		{
			prev = tmp;
			tmp = tmp->next;
			if(!tmp)
				break;
			tmp->remainder--;
			if(tmp->remainder < 1)
			{
				tmp->callback(tmp->callback_ptr);
				if(tmp->repeat)
					tmp->remainder = tmp->duration;
				else
				{
					prev->next = tmp->next;
					kfree(tmp); // Ordering is important here
					tmp = prev;
				}
			}
		}
	}

	outportb(0x70, 0x0C);
	inportb(0x71); // Tells us whichs32interrupt happened. Uninportant for now.
}

void rtc_write_time(void)
{
	disable_interrupts();	

	//force binary mode
	outportb(0x70,0x0B);//the first status port
	u8int status = inportb(0x71);
	status |= 0x20;
	outportb(0x71,status);

	//write the time
	outportb(0x70,0x00);//seconds
	outportb(0x71, (u8int)seconds);
	
	outportb(0x70,0x02);//minutes
	outportb(0x71, (u8int)minutes);

	outportb(0x70,0x04);//hours
	outportb(0x71, (u8int)hours);

	outportb(0x70,0x07);//days (in the month)
	outportb(0x71, (u8int)days);

	outportb(0x70,0x08);//months
	outportb(0x71, (u8int)months);

	outportb(0x70,0x09);//years
	outportb(0x71, (u8int)(years - 2000));

	enable_interrupts();
}

void rtc_read_time(void)
{
	disable_interrupts();

	//get the date and time from the CMOS chip
	outportb(0x70,0x00);//get seconds
	seconds = inportb(0x71);
	
	outportb(0x70,0x02);//get minutes
	minutes = inportb(0x71);

	outportb(0x70,0x04);//get hours
	hours = inportb(0x71);

	outportb(0x70,0x07);//get days (in the month)
	days = inportb(0x71);

	outportb(0x70,0x08);//get months
	months = inportb(0x71);

	outportb(0x70,0x09);//get years
	years = inportb(0x71);
	
	//if in BCD mode convert
	outportb(0x70,0x0B);//the first status port
	u8int status = inportb(0x71);
	if(!(status & 0x20))//if the clock is in BCD mode
	{
		seconds = BCD2BIN(seconds);
		minutes = BCD2BIN(minutes);
		hours = BCD2BIN(hours);
		days = BCD2BIN(days);
		months = BCD2BIN(months);
		years = BCD2BIN(years);
	}

	years += 2000; //we are in the 21'st century ya' know...

	enable_interrupts();
}

void rtc_time(u32int control, u32int value)
{
	switch(control)
	{
		case RTC_YEARS:
			years = value + 2000;
		break;
		case RTC_MONTHS:
			months = value;
		break;
		case RTC_DAYS:
			days = value;
		break;
		case RTC_HOURS:
			hours = value;
		break;
		case RTC_MINUTES:
			minutes = value;
		break;
	}

	rtc_write_time();
} 

void rtc_program_timer(u8int rate)
{
	disable_interrupts();

	// set the periodics32interrupt rate
	outportb(0x70, 0x0A);
	u8int rega = inportb(0x71);
	outportb(0x70, 0x0A);
	outportb(0x71, (u8int)((rega & 0xF0) | rate)); //write only our rate to A. Note, rate is the bottom 4 bits.

	// turn on the periodics32interrupt
	outportb(0x70, 0x0B);
	u8int regb = inportb(0x71);
	outportb(0x70, 0x0B);
	outportb(0x71, (u8int)(regb | 0x40)); //This turns on bit 6 of register D

	enable_interrupts();
}

void rtc_reg_callback(callback_type_t callback, u32int* callback_ptr, u32int duration, u32int repeat)
{
	timed_callback_t* new_callback = (timed_callback_t*)kmalloc(sizeof(timed_callback_t));
	new_callback->callback = callback;
	new_callback->callback_ptr = callback_ptr;
	new_callback->duration = duration;
	new_callback->remainder = duration;
	new_callback->repeat = repeat;
	new_callback->next = null_callback.next;
	null_callback.next = new_callback;
}

void rtc_unreg_callback(callback_type_t callback)
{
	timed_callback_t* tmp = &null_callback;
	timed_callback_t* prev = 0;
	while(tmp->next != &null_callback)
	{
		prev = tmp;
		tmp = tmp->next;
		if(tmp->callback == callback)
		{
			prev->next = tmp->next;
			kfree(tmp);
			return;
		}
	}
}

void init_rtc(void)
{
#ifdef FLUIDIUM_DEBUG
	write_string("RTC: Init\n");
#endif

	rtc_read_time();

#ifdef FLUIDIUM_DEBUG
	write_string("RTC: Current Time: ");
	write_string(month_names[months]); write_string(" ");
	write_number(days); write_string(", ");
	write_number(years); write_string(" at ");
	write_number(hours); write_string(":");
	write_number(minutes); write_string(":");
	write_number(seconds); write_string("\n");
#endif	

   irq_install_handler(8, rtc_handler);
	rtc_program_timer(15);

	null_callback.callback = 0;
	null_callback.next = &null_callback;

	rtc_initalized = 1;
}
